/*
 * Wazuh Vulnerability scanner - Scan Orchestrator
 * Copyright (C) 2015, Wazuh Inc.
 * March 4, 2024.
 *
 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public
 * License (version 2) as published by the FSF - Free Software
 * Foundation.
 */

#ifndef _SCAN_OS_ALERT_DETAILS_BUILDER_HPP
#define _SCAN_OS_ALERT_DETAILS_BUILDER_HPP

#include "chainOfResponsability.hpp"
#include "databaseFeedManager.hpp"
#include "numericHelper.h"
#include "scanContext.hpp"
#include "timeHelper.h"

/**
 * @brief TScanOsAlertDetailsBuilder class.
 * This class is responsible for building the alert details for the OS scan.
 * It receives the scan context and the database feed manager and returns the scan context with the alert details.
 * It will generate alerts for detected vulnerabilities in the OS.
 * Its neccessary to have the context->m_elements populated with the cve and the operation.
 * The operation can be "DELETED" or "INSERTED".
 * Its also necessary to have the context->m_matchConditions populated with the cve and the condition.
 * The condition can be "LessThanOrEqual", "LessThan", "DefaultStatus" or "Equal".
 * The alert details will be populated in the context->m_alerts.
 *
 * @tparam TDatabaseFeedManager database feed manager type.
 * @tparam TScanContext scan context type.
 */
template<typename TDatabaseFeedManager = DatabaseFeedManager, typename TScanContext = ScanContext>
class TScanOsAlertDetailsBuilder final : public AbstractHandler<std::shared_ptr<TScanContext>>
{
private:
    std::shared_ptr<TDatabaseFeedManager> m_databaseFeedManager;

public:
    // LCOV_EXCL_START
    /**
     * @brief ScanOsAlertDetailsBuilder constructor.
     *
     * @param databaseFeedManager Database feed manager.
     */
    explicit TScanOsAlertDetailsBuilder(std::shared_ptr<TDatabaseFeedManager>& databaseFeedManager)
        : m_databaseFeedManager(databaseFeedManager)
    {
    }

    /**
     * @brief Class destructor.
     *
     */
    ~TScanOsAlertDetailsBuilder() = default;
    // LCOV_EXCL_STOP

    /**
     * @brief Handles request and passes control to the next step of the chain.
     *
     * @param data Scan context.
     * @return std::shared_ptr<ScanContext> Abstract handler.
     */
    std::shared_ptr<TScanContext> handleRequest(std::shared_ptr<TScanContext> data) override
    {
        // We only generate alerts if its not the first scan(baseline).
        if (!data->m_isFirstScan)
        {
            for (const auto& element : data->m_elements)
            {
                const auto& cve {element.first};
                const auto& operation = element.second.at("operation").template get_ref<const std::string&>();
                const auto elementOperation {operation.compare("DELETED") == 0    ? ElementOperation::Delete
                                             : operation.compare("INSERTED") == 0 ? ElementOperation::Insert
                                                                                  : ElementOperation::Unknown};

                FlatbufferDataPair<VulnerabilityDescription> returnData;
                m_databaseFeedManager->getVulnerabiltyDescriptiveInformation(cve, returnData);

                if (returnData.data)
                {
                    const std::string cvssVersion {returnData.data->scoreVersion()->str()};
                    const std::string scoreVersion {"cvss" + cvssVersion.substr(0, 1)};
                    nlohmann::json json;

                    if (elementOperation == ElementOperation::Insert)
                    {
                        if (!cvssVersion.empty())
                        {
                            nlohmann::json vectorObj;
                            if (scoreVersion.compare("cvss2") == 0)
                            {
                                vectorObj["access_complexity"] = returnData.data->accessComplexity()->str();
                                vectorObj["authentication"] = returnData.data->authentication()->str();
                            }
                            else if (scoreVersion.compare("cvss3") == 0)
                            {
                                vectorObj["attack_vector"] = returnData.data->attackVector()->str();
                                vectorObj["privileges_required"] = returnData.data->privilegesRequired()->str();
                                vectorObj["scope"] = returnData.data->scope()->str();
                                vectorObj["user_interaction"] = returnData.data->userInteraction()->str();
                            }

                            vectorObj["availability"] = returnData.data->availabilityImpact()->str();
                            vectorObj["confidentiality_impact"] = returnData.data->confidentialityImpact()->str();
                            vectorObj["integrity_impact"] = returnData.data->integrityImpact()->str();

                            json["vulnerability"]["cvss"][scoreVersion]["vector"] = std::move(vectorObj);
                        }

                        json["vulnerability"]["assigner"] = returnData.data->assignerShortName()->str();
                        json["vulnerability"]["cwe_reference"] = returnData.data->cweId()->str();
                        json["vulnerability"]["package"]["source"] = "OS";
                        json["vulnerability"]["rationale"] = returnData.data->description()->str();

                        json["vulnerability"]["status"] = "Active";

                        // title = CVE-XXXX-XXXX affects <package name>
                        std::string title {cve};
                        title.append(" affects ");
                        title.append(data->osName());
                        json["vulnerability"]["title"] = title;
                    }
                    else if (elementOperation == ElementOperation::Delete)
                    {
                        json["vulnerability"]["status"] = "Solved";

                        // title = CVE-XXXX-XXXX affecting <package name> was solved
                        std::string title {cve};
                        title.append(" affecting ");
                        title.append(data->osName());
                        title.append(" was solved");
                        json["vulnerability"]["title"] = title;
                    }
                    else
                    {
                        throw std::runtime_error("Unknown operation");
                    }

                    json["vulnerability"]["cve"] = cve;
                    if (!cvssVersion.empty())
                    {
                        json["vulnerability"]["cvss"][scoreVersion]["base_score"] =
                            Utils::floatToDoubleRound(returnData.data->scoreBase(), 2);
                    }
                    json["vulnerability"]["enumeration"] = "CVE";
                    json["vulnerability"]["package"]["architecture"] = data->osArchitecture();
                    json["vulnerability"]["package"]["name"] = data->osName();
                    json["vulnerability"]["package"]["version"] = data->osVersion();
                    json["vulnerability"]["published"] = returnData.data->datePublished()->str();
                    json["vulnerability"]["reference"] = returnData.data->reference()->str();
                    json["vulnerability"]["severity"] = Utils::toSentenceCase(returnData.data->severity()->str());

                    json["vulnerability"]["type"] = "Packages";
                    json["vulnerability"]["updated"] = returnData.data->dateUpdated()->str();

                    auto it = data->m_matchConditions.find(cve);
                    if (it != data->m_matchConditions.end())
                    {
                        if (it->second.condition == MatchRuleCondition::LessThanOrEqual)
                        {
                            json["vulnerability"]["package"]["condition"] =
                                "Package less than or equal to " + it->second.version;
                        }
                        else if (it->second.condition == MatchRuleCondition::LessThan)
                        {
                            json["vulnerability"]["package"]["condition"] = "Package less than " + it->second.version;
                        }
                        else if (it->second.condition == MatchRuleCondition::DefaultStatus)
                        {
                            json["vulnerability"]["package"]["condition"] = "Package default status";
                        }
                        else if (it->second.condition == MatchRuleCondition::Equal)
                        {
                            json["vulnerability"]["package"]["condition"] = "Package equal to " + it->second.version;
                        }
                        else
                        {
                            logDebug2(WM_VULNSCAN_LOGTAG, "Unknown match condition");
                        }
                    }

                    data->m_alerts[cve] = json;
                }
            }
        }
        return AbstractHandler<std::shared_ptr<TScanContext>>::handleRequest(std::move(data));
    }
};

using ScanOsAlertDetailsBuilder = TScanOsAlertDetailsBuilder<>;

#endif // _SCAN_OS_ALERT_DETAILS_BUILDER_HPP
